package gr.atc.epart;

import gr.atc.epart.actions.Action;
import gr.atc.epart.actions.ActionComment;
import gr.atc.epart.actions.Deliberation;
import gr.atc.epart.actions.Petition;
import gr.atc.epart.actions.PetitionSignature;
import gr.atc.epart.actions.Survey;
import gr.atc.epart.actions.Action.Type;
import gr.atc.epart.net.ActionsPage;
import gr.atc.epart.net.CreateFailedException;
import gr.atc.epart.net.RetrievalFailedException;
import gr.atc.epart.net.ActionsPage.SortBy;
import gr.atc.epart.users.User;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.w3c.dom.Document;
import com.google.android.maps.GeoPoint;
import android.util.Log;
import android.util.Pair;


public class EParticipationServicesProvider
{
private static EParticipationServicesProvider _instance = new EParticipationServicesProvider();
	// the eParticipation platform can access services provided either by Google or
	// by the back end
    private GoogleServicesProvider _googleServices = new GoogleServicesProvider();
    private BackEndServicesProvider _backendServices = new BackEndServicesProvider(); 
    
    private String _backendAuthToken; 
    
    private EParticipationServicesProvider()
    {
    		
    }
    
     public static EParticipationServicesProvider getInstance()
    {
    	return _instance;
    }
    
    
    //////////////////////////////////////////////////////////
    // Functionality available to the eParticipation platform
    //////////////////////////////////////////////////////////
    
    //
    // Functionality provided by the Back-end only
    //
    //
    public int createPetition(Petition petition)
    {
    	try
    	{
    		JSONObject petitioncreationResult = _backendServices.createPetition(petition);
    		boolean success = petitioncreationResult.getBoolean("success");
	    	
	    	if (!success)
	    		return 0;
	    	else
	    		return petitioncreationResult.getInt("actionId");
	    	
    	}
    	catch (Exception e)
    	{
    	
    		return 0;
    	}
    	
    }
           
    public Pair<Petition,Boolean> getPetition(int id,String userName) throws RetrievalFailedException
    {
    	try
    	{
    		
	    	JSONObject petitionData = _backendServices.getPetition(id,userName);
	
	    	boolean success = petitionData.getBoolean("success");
	    	
	    	if (!success)
	    	{
	    		throw new RetrievalFailedException(String.format("Cannot retrieve petition with id=%d. Service failed",id));
	    	}

	    	boolean signed= false;
	    	
	    	if (petitionData.has("signed"))
	    		signed = petitionData.getBoolean("signed");

	    	//
	    	// get all the details of the petition and create a Petition object
	    	//
	    	JSONObject petitionInfo = petitionData.getJSONObject("petition");
	    	
	    	return Pair.create(parsePetitionJSON(petitionInfo),signed);
    	}
    	catch (Exception e)
    	{
    		throw new RetrievalFailedException(String.format("Cannot retrieve petition with id=%d. Reason [%s]",id,e.getMessage()));
    	}

    }
        
    public List<PetitionSignature> getSignaturesForPetition(int petitionId)
	{
    	//TODO:: the server script is not ready yet...returning some sample data
    	List<PetitionSignature> signatures = new LinkedList<PetitionSignature>();
   
    	Random generator = new Random(7865464);
    	for (int i=0;i<generator.nextInt(8);i++)
    	{
    		String user = String.format("user-%d",i);
    		String description = String.format("sample description from %s", user);
    		PetitionSignature signature = new PetitionSignature(user,description);
    	
    		signatures.add(signature);
    	}
    	
		return signatures;
	}
    
    public boolean voteForPetition(int petitionId, String userName,String comment) throws CreateFailedException
	{
    	try
    	{
    		
	    	JSONObject petitionVoteData = _backendServices.voteForPetition(petitionId, userName,comment);
	
	    	boolean success = petitionVoteData.getBoolean("success");
	    	
	    	if (!success)
	    	{
	    		throw new CreateFailedException(String.format("Cannot submit vote for petition with id=%d. Service failed",petitionId));
	    	}
	    	
	    	return true;
    	}
    	catch (Exception e)
    	{
    		throw new CreateFailedException(String.format("Cannot submit vote for petition with id=%d. Reason [%s]",petitionId,e.getMessage()));
    	}
    	 
	}
    
    public boolean commentOnAction(int actionId, String userName,String comment) throws CreateFailedException
	{
    	try
    	{
    		
	    	JSONObject actionCommentData = _backendServices.commentOnAction(actionId, userName,comment);
	
	    	boolean success = actionCommentData.getBoolean("success");
	    	
	    	if (!success)
	    	{
	    		throw new CreateFailedException(String.format("Cannot submit comment for action with id=%d. Service failed",actionId));
	    	}
	    	
	    	return true;
    	}
    	catch (Exception e)
    	{
    		throw new CreateFailedException(String.format("Cannot submit comment for action with id=%d. Reason [%s]",actionId,e.getMessage()));
    	}
    	 
	}

    public List<ActionComment> getComments(int actionId, int pageNumber,int pageSize) throws RetrievalFailedException
    {
    	
    		
    	try
    	{
    		List<ActionComment> actionCommentsList = new LinkedList<ActionComment>();
    		
	    	JSONObject commentsData = _backendServices.getComments(actionId,pageNumber,pageSize);
	
	    	boolean success = commentsData.getBoolean("success");
	    	
	    	if (!success)
	    	{
	    		throw new RetrievalFailedException(String.format("Cannot retrieve comments for action with id=%d. Service failed",actionId));
	    	}

	    	JSONArray  comments = commentsData.getJSONArray("comments");

	    	for (int i=0;i<comments.length();i++)
	    	{
	    		JSONObject comment = comments.getJSONObject(i);

	    		String userName = comment.getString("user");
	    		String content  = comment.getString("comment");
	    		
	    		ActionComment  actionComment = new ActionComment(userName,content);
	    		
				SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
				
				if (comment.has("date") && comment.getString("date").length()>0)
					actionComment.setSubmitDate(formatter.parse(comment.getString("date")));
	    		
	    		actionCommentsList.add(actionComment);
	    	}
	    	
	    	return actionCommentsList;
	    	
    	}
    	catch (Exception e)
    	{
    		throw new RetrievalFailedException(String.format("Cannot retrieve comments for action with id=%d. Reason [%s]",actionId,e.getMessage()));
    	}
    	
    }
    
    public ActionsPage getActions(Type actionType, String searchText,String creator,
    		Date startDate, Date stopDate, String [] tags, SortBy sortBy,int pageNumber,int pageSize) throws RetrievalFailedException
    {
    	try
    	{
    		
     	// read from the database the petition with id
	    JSONObject actionData = _backendServices.getAction(actionType,searchText,creator,startDate,stopDate,tags,sortBy,pageNumber,pageSize);

    	boolean success = actionData.getBoolean("success");
    	
    	if (!success)
    	{
    		throw new RetrievalFailedException(String.format("Cannot retrieve action. Service failed with reason \"%s\"",actionData.getString("reason")));
    	}

    	//
    	// calculate paging
    	//
    	int totalMatches = 0;
    	try
    	{
    		totalMatches = Integer.parseInt(actionData.getString("total"));
    	}
    	catch (NumberFormatException e)
    	{
    		totalMatches = 0;
    	}
    	
    	 
    	int totalPages = totalMatches / pageSize;
    	if ((totalMatches % pageSize) != 0)
    		totalPages++;
    	
    	
    	//
    	// get all the details of the action and create a ActionsPage object
    	//
    	List<Action> actionList = new LinkedList<Action>();
    	
    	JSONArray  actions = actionData.getJSONArray("actions");
    	
    	for (int i=0;i<actions.length();i++)
    	{
    		JSONObject actionInfo = actions.getJSONObject(i);
    		//based on the type of the action a different type of class is instantiated 

    		Action   action;
    		if (actionInfo.getString("type").equalsIgnoreCase("petition"))
    		{
    			action = parsePetitionJSON(actionInfo);
    		}
    		else if (actionInfo.getString("type").equalsIgnoreCase("survey"))
    		{
    			action = parseSurveyJSON(actionInfo);
    		}
    		else if (actionInfo.getString("type").equalsIgnoreCase("deliberation"))
    		{
    			action = parseDeliberationJSON(actionInfo);
    		}
    		else 
    		{
    			throw new RetrievalFailedException(String.format("Unknown action type %s",actionInfo.getString("type")));
    		}
    			
    		// add to action list
    		actionList.add(action);
    	}

    	// all actions processed, add them to the page
    	ActionsPage actionsPage = new ActionsPage();
    	actionsPage.setActions(actionList);
    	actionsPage.setTotalPages(totalPages);
    	actionsPage.setPageNumber(pageNumber);
    	actionsPage.setTotalActionCount(totalMatches);

    	return actionsPage;
    	}
    	catch (Exception e)
    	{
    		throw new RetrievalFailedException(String.format("Cannot retrieve action. Reason [%s]",e.getMessage()));
    	}
    }
    
    public boolean createAction(Type actionType, String name,String description, String creator, Date startDate, 
    		Date stopDate,String[] tags, boolean isLoginRequired, int seriesId) throws CreateFailedException
	{
    	
    	try
    	{
    		// read from the database the petition with id
    		JSONObject actionData = _backendServices.createAction(actionType,name,description,creator,startDate,stopDate,tags,isLoginRequired,seriesId);

    		boolean success = actionData.getBoolean("success");

    		if (!success)
    		{
    			throw new CreateFailedException(String.format("Service failed with reason \"%s\"",actionData.getString("reason")));
    		}
    		return true;
    		
    	}catch (Exception e)
    	{
    		throw new CreateFailedException(String.format("Cannot create action. Reason [%s]",e.getMessage()));
    	}
	}
    
    //
    //
    // functionality provided by Google APIs and services
    //
    public List<GeoCodeSuggestion>  geocodeAddress(String address,String language)
    {
    	// geocode works in  synchronous mode (Blocking)
    	String geocodingResponse = _googleServices.geoCode(address, language);
    	
    	return parseGeocodingResponse(geocodingResponse); 
    }
        
    public String reverseGeocode(GeoPoint point,String language,int maxMatches)
    {
    	String reverseGeocodingResponse = _googleServices.reverseGeocode(point.getLatitudeE6() / 1E6 , point.getLongitudeE6() / 1E6, language); 
    	
    	return parseReverseGeocodingReponse(reverseGeocodingResponse); 
    }
    
   
	//
    //
    // Functionality provided both by Backend and Google
    //
    //
    //
    
    public boolean logInToServices(String username, String password)
    {
    	// login to the backend first
    	String _backendAuthToken = _backendServices.logIn(username, password);

    	// TODO:: the system login response is possibly a JSON document, with some authentication token?
    	
    	// TODO:: the client must also login to google in order to access some Google services.
    	// retrieve from the backend the google credentials needed to access 
    	// the Google services
    	
    	// TODO::login to Google
    	// 
    	
    	return false;
    }
    
    
    ///
    //
    // Helper functions for supporting the above services
    //
    //
    
    private void parseBaseActionJSON(Action action, JSONObject actionInfo) throws Exception
	{
    	  //
    	  // Parse the data common to all Action derived-classes
    	  //
    	     
	    	action.setId(actionInfo.getInt("id"));
	    	action.setName(actionInfo.getString("name"));
	    	action.setDescription(actionInfo.getString("description"));
	    	
			User user = new User();
			user.setName(actionInfo.getString("userName"));
			// TODO:: do we need this?
			//String userId = actionInfo.getString("userId");
			action.setCreator(user);
			
			SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
			
			if (actionInfo.has("dateCreated") && actionInfo.getString("dateCreated").length()>0)
				action.setDateCreated(formatter.parse(actionInfo.getString("dateCreated")));
			
			if (actionInfo.has("dateModified") && actionInfo.getString("dateModified").length()>0)
				action.setDateModified(formatter.parse(actionInfo.getString("dateModified")));	
			
			if (actionInfo.has("tags"))
				action.setTags(actionInfo.getString("tags").split(" "));

			action.updatePopularity(Integer.parseInt(actionInfo.getString("popularity")));
			action.updateVisits(Integer.parseInt(actionInfo.getString("visits")));
			
	}
        
    private Petition parsePetitionJSON(JSONObject petitionInfo) throws RetrievalFailedException
    {
    	try
    	{
    		//
    		// create a new empty object
    		//
    		Petition petition = new Petition();
    		
    		//
    		// fill-in the base data
    		//
	    	parseBaseActionJSON(petition,petitionInfo);
	    	
	    	//
	    	// continue with the petition specific data
	    	//
	    	SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");

	    	if (petitionInfo.has("start") && petitionInfo.getString("start").length()>0)
	    		petition.setStartDate(formatter.parse(petitionInfo.getString("start")));
	    	
	    	if (petitionInfo.has("stop") && petitionInfo.getString("stop").length()>0)
	    		petition.setStopDate(formatter.parse(petitionInfo.getString("stop")));

			// TODO:: can we do this to be boolean?

	    	if (petitionInfo.has("requiresLogIn"))
	    		petition.setLogInRequired(petitionInfo.getString("requiresLogIn").equalsIgnoreCase("1"));

			// get only the number of signatures
	    	// the server does not return the signatures details
	    	// an extra call is required 
	    	// see getSignaturesForPetition()
	    	if (petitionInfo.has("signatures") && petitionInfo.getString("signatures")!= null && !petitionInfo.getString("signatures").equalsIgnoreCase("null"))
	    		petition.setSignaturesCount(Integer.parseInt(petitionInfo.getString("signatures")));

	    	return petition;
	    	
    	}catch(Exception e)
    	{
    		throw new RetrievalFailedException(e.getMessage());
    	}
    
    }
        
    private Survey  parseSurveyJSON(JSONObject surveyInfo) throws RetrievalFailedException
    {
    	try
    	{
    		//
    		// create a new empty object
    		//
    		Survey survey = new Survey();
    		
    		//
    		// fill-in the base data
    		//
	    	parseBaseActionJSON(survey,surveyInfo);
	    	
	    	//
	    	// continue with the petition specific data
	    	//
	    	SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");

	    	if (surveyInfo.has("start") && surveyInfo.getString("start").length()>0)
	    		survey.setStartDate(formatter.parse(surveyInfo.getString("start")));
	    	
	    	if (surveyInfo.has("stop") && surveyInfo.getString("stop").length()>0)
	    		survey.setStopDate(formatter.parse(surveyInfo.getString("stop")));

			// TODO:: can we do this to be boolean?
	    	if (surveyInfo.has("requiresLogIn"))
	    		survey.setLogInRequired(surveyInfo.getString("requiresLogIn").equalsIgnoreCase("1"));

	    	//
	    	// Survey is implemented using Google moderator and the series id 
	    	// is the link to the moderator entry
	    	//
	    	if (surveyInfo.has("seriesid"))
	    	{
	    		
	    		survey.setModeratorSeriesId(Integer.parseInt(surveyInfo.getString("seriesid")));
	    	}
	    	return survey;
	    	
    	}catch(Exception e)
    	{
    		throw new RetrievalFailedException(e.getMessage());
    	}
    	
    }

    private Deliberation parseDeliberationJSON(JSONObject deliberationInfo) throws RetrievalFailedException
    {
       	try
    	{
    		//
    		// create a new empty object
    		//
       		Deliberation deliberation = new Deliberation();
    		
    		//
    		// fill-in the base data
    		//
	    	parseBaseActionJSON(deliberation,deliberationInfo);
	    	
	    	//
	    	// Deliberation is implemented using Google moderator and the series id 
	    	// is the link to the moderator entry
	    	//
	    	
	    	if (deliberationInfo.has("seriesid"))
	    	{
	    		deliberation.setModeratorSeriesId(Integer.parseInt(deliberationInfo.getString("seriesid")));
	    	}
	    	
	    	// UNCOMMENT THIS LINE TO SEE A TEST SERIES 
	    	//deliberation.setModeratorSeriesId(140091);
	    	
	    	return deliberation;
	    	
    	}catch(Exception e)
    	{
    		throw new RetrievalFailedException(e.getMessage());
    	}
    }
    
    //
    // Geocoding results parsing 
    //
    private List<GeoCodeSuggestion> parseGeocodingResponse(String geocodingResponse)
    {
    	List<GeoCodeSuggestion>  geocodeResults = new LinkedList<GeoCodeSuggestion>();
    	
    	if (geocodingResponse == null)
    		return geocodeResults;
    	
		try
		{
		   
			JSONObject  geocodingData = new JSONObject(geocodingResponse);

			int statusCode = geocodingData.getJSONObject("Status").getInt("code");
			
			if (statusCode == 200)
			{
				// succesfull geocoding
				JSONArray  placemarks = geocodingData.getJSONArray("Placemark");
				for (int i = 0;i<placemarks.length();i++)
				{
					JSONObject placemark = placemarks.getJSONObject(i);
					
					String address = placemark.getString("address");
					double latitude = placemark.getJSONObject("Point").getJSONArray("coordinates").getDouble(1);
					double longitude = placemark.getJSONObject("Point").getJSONArray("coordinates").getDouble(0);

					GeoCodeSuggestion suggestion = new GeoCodeSuggestion(address,latitude,longitude);
					geocodeResults.add(suggestion);	
				}
			}

		} catch (JSONException e)
		{
			e.printStackTrace();
		}
    	
    	
    	return geocodeResults;
    }
    
    //
    // Reverse geocoding results parsing
    //
    
    private String parseReverseGeocodingReponse(String reverseGeocodingResponse)
	{
		if (reverseGeocodingResponse == null)
			return " ";
		
		try
		{
		   
			JSONObject  reverseGeocodingData = new JSONObject(reverseGeocodingResponse);
			int statusCode = reverseGeocodingData.getJSONObject("Status").getInt("code");
			
			if (statusCode == 200)
			{
				// succesfull reverse geocoding
				JSONArray  placemarks = reverseGeocodingData.getJSONArray("Placemark");
				return placemarks.getJSONObject(0).getString("address");
			}
			
		} catch (JSONException e)
		{
			e.printStackTrace();
		}
		return "";
	}

	


    
    
    
}
